﻿#region License
/* ---------------------------------------------------------------- *
 *
 * This file is part of the API Concepts Framework
 * http://apiconceptsframework.codeplex.com
 *
 * Copyright (C) 2014 eva Kühn, Thomas Scheller, Vienna University of Technology
 * http://www.complang.tuwien.ac.at/eva
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see http://www.gnu.org/licenses/
 *
 * ---------------------------------------------------------------- */
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using ApiConceptsFramework.Contract;

namespace ApiConceptsFramework.HCs
{
	public class FluentInterface : IHighLevelConcept
	{
		public const int MinClassChangesForComplexChain = 2;
		public const int ComplexityPerClassChangeInChain = 10;
		public const int AdditionalComplexityForFirstUsage = 5;
		public const int ReducedComplexityOfReturnValue = -2;

		public HcEvaluationResult Evaluate(List<Assembly> apiAssemblies, IEnumerable<ConceptInstance> data, IHcEvaluationInput hcInputData, IEnumerable<HcEvaluationResult> otherHcData, IDE ide = IDE.VisualStudioOrSimilar)
		{
			if (hcInputData == null)
				return null;
			var pd = hcInputData as FluentInterfaceInputData;
			if (pd == null)
				throw new ArgumentException("hcInputData must be FluentInterfaceInputData", "hcInputData");

			var fluentMethodCalls = FindFluentMethodCalls(data, pd.FluentMethodChains).ToList();
			if (fluentMethodCalls.Count == 0)
				return null;
			
			ApplyDataToFluentMethodCalls(fluentMethodCalls, ide);

			return new HcEvaluationResult { HighLevelConcept = this, ComplexityData = CalculateComplexityOfFluentMethodChains(pd.FluentMethodChains).ToList() };
		}

		private static IEnumerable<ConceptInstance> FindFluentMethodCalls(IEnumerable<ConceptInstance> data, IEnumerable<FluentMethodChain> fluentMethodChains)
		{
			var fluentMethods = fluentMethodChains.SelectMany(f => f.Methods).Distinct().ToList();
			return data.Where(c => fluentMethods.Contains(c.Target.Member));
		}

		private void ApplyDataToFluentMethodCalls(IEnumerable<ConceptInstance> fluentMethodCalls, IDE ide)
		{
			foreach (var c in fluentMethodCalls)
			{
				if (((MethodInfo)c.Target.Member).ReturnType == typeof(void))
					throw new Exception("Method " + c.Target.Member + " has no return parameter, so it cannot be a fluent method!");

				CalculateIncreasedComplexityForFirstUsage(c);
			}
		}

		private void CalculateIncreasedComplexityForFirstUsage(ConceptInstance c)
		{
			//fluent method has increased complexity when it is first used because the user needs to understand how and when in the chain the method can be used
			c.HcData.Add(new HcComplexityData
					{
						HighLevelConcept = this,
						UsageComplexity = CalcComplexityWithUsagesAndLearningEffect(c, ReducedComplexityOfReturnValue) + (c.PreviousUsages == 0 ? AdditionalComplexityForFirstUsage : 0),
						Description = "less relevance of return parameter, increased complexity of first usage"
					});
		}

		private static float CalcComplexityWithUsagesAndLearningEffect(ConceptInstance c, float complexity)
		{
			return LearningEffect.Usage.CalculateComplexityForMultipleUsages(complexity, c.NumberOfUsages, c.PreviousUsages);
		}

		private IEnumerable<HcComplexityData> CalculateComplexityOfFluentMethodChains(IEnumerable<FluentMethodChain> fluentMethodChains)
		{
			foreach (var methodChain in fluentMethodChains)
			{
				int numberOfClassChangesInChain = CalculateNumberOfClassChangesInChain(methodChain);
				if (numberOfClassChangesInChain <= MinClassChangesForComplexChain)
					continue;
				var complexityPerUsage = (numberOfClassChangesInChain - MinClassChangesForComplexChain)*
				                         ComplexityPerClassChangeInChain;

				var complexityOverall = LearningEffect.Usage.CalculateComplexityForMultipleUsages(complexityPerUsage, methodChain.NumberOfUsages, methodChain.PreviousUsages);
				yield return new HcComplexityData
								 {
									 HighLevelConcept = this,
									 UsageComplexity = complexityOverall,
									 Description = "Complex Method chain: " + string.Join(".", methodChain.Methods.Select(m => m.Name)) + " (" + numberOfClassChangesInChain + " class changes, " + methodChain.NumberOfUsages + " usages, " + methodChain.PreviousUsages + " prev)"
								 };
			}
		}

		private int CalculateNumberOfClassChangesInChain(FluentMethodChain methodChain)
		{
			int changes = 0;
			Type prevClass = null;
			foreach (var method in methodChain.Methods)
			{
				var nextClass = method.ReturnType;
				if (prevClass != null && prevClass != nextClass)
					changes++;
				prevClass = nextClass;
			}
			return changes;
		}

		public Type InputDataType
		{
			get { return typeof(FluentInterfaceInputData); }
		}
	}
}
